/*******************************************************************************
 * Copyright (c) 2011, 2016 Eurotech and/or its affiliates
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Eurotech
 *******************************************************************************/
package org.eclipse.kura.web.client.ui.Firewall;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.kura.web.client.messages.Messages;
import org.eclipse.kura.web.client.ui.EntryClassUi;
import org.eclipse.kura.web.client.ui.Tab;
import org.eclipse.kura.web.client.util.FailureHandler;
import org.eclipse.kura.web.client.util.TextFieldValidator.FieldType;
import org.eclipse.kura.web.shared.model.GwtFirewallNatEntry;
import org.eclipse.kura.web.shared.model.GwtFirewallNatMasquerade;
import org.eclipse.kura.web.shared.model.GwtFirewallNatProtocol;
import org.eclipse.kura.web.shared.model.GwtXSRFToken;
import org.eclipse.kura.web.shared.service.GwtNetworkService;
import org.eclipse.kura.web.shared.service.GwtNetworkServiceAsync;
import org.eclipse.kura.web.shared.service.GwtSecurityTokenService;
import org.eclipse.kura.web.shared.service.GwtSecurityTokenServiceAsync;
import org.gwtbootstrap3.client.shared.event.ModalHideEvent;
import org.gwtbootstrap3.client.shared.event.ModalHideHandler;
import org.gwtbootstrap3.client.ui.Alert;
import org.gwtbootstrap3.client.ui.Button;
import org.gwtbootstrap3.client.ui.FormGroup;
import org.gwtbootstrap3.client.ui.FormLabel;
import org.gwtbootstrap3.client.ui.ListBox;
import org.gwtbootstrap3.client.ui.Modal;
import org.gwtbootstrap3.client.ui.ModalBody;
import org.gwtbootstrap3.client.ui.ModalFooter;
import org.gwtbootstrap3.client.ui.TextBox;
import org.gwtbootstrap3.client.ui.Tooltip;
import org.gwtbootstrap3.client.ui.constants.ValidationState;
import org.gwtbootstrap3.client.ui.gwt.CellTable;
import org.gwtbootstrap3.client.ui.html.Span;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SingleSelectionModel;

public class NatTabUi extends Composite implements Tab {

    private static NatTabUiUiBinder uiBinder = GWT.create(NatTabUiUiBinder.class);

    interface NatTabUiUiBinder extends UiBinder<Widget, NatTabUi> {
    }

    private static final Messages MSGS = GWT.create(Messages.class);

    private final GwtSecurityTokenServiceAsync gwtXSRFService = GWT.create(GwtSecurityTokenService.class);
    private final GwtNetworkServiceAsync gwtNetworkService = GWT.create(GwtNetworkService.class);

    private final ListDataProvider<GwtFirewallNatEntry> natDataProvider = new ListDataProvider<GwtFirewallNatEntry>();
    final SingleSelectionModel<GwtFirewallNatEntry> selectionModel = new SingleSelectionModel<GwtFirewallNatEntry>();

    private boolean m_dirty;

    private GwtFirewallNatEntry newNatEntry;
    private GwtFirewallNatEntry editNatEntry;

    @UiField
    Button apply, create, edit, delete;
    @UiField
    Alert notification;
    @UiField
    CellTable<GwtFirewallNatEntry> natGrid = new CellTable<GwtFirewallNatEntry>();

    @UiField
    Modal natForm;
    @UiField
    FormGroup groupInput, groupOutput, groupProtocol, groupSource, groupDestination, groupEnable;
    @UiField
    FormLabel labelInput, labelOutput, labelProtocol, labelSource, labelDestination, labelEnable;
    @UiField
    Tooltip tooltipInput, tooltipOutput, tooltipProtocol, tooltipSource, tooltipDestination, tooltipEnable;
    @UiField
    TextBox input, output, source, destination;
    @UiField
    ListBox protocol, enable;
    @UiField
    Button submit, cancel;

    public NatTabUi() {
        initWidget(uiBinder.createAndBindUi(this));
        initButtons();
        initTable();
        initModal();
    }

    //
    // Public methods
    //
    @Override
    public void refresh() {
        EntryClassUi.showWaitModal();
        this.natDataProvider.getList().clear();
        this.gwtXSRFService.generateSecurityToken(new AsyncCallback<GwtXSRFToken>() {

            @Override
            public void onFailure(Throwable ex) {
                EntryClassUi.hideWaitModal();
                FailureHandler.handle(ex);
            }

            @Override
            public void onSuccess(GwtXSRFToken token) {
                NatTabUi.this.gwtNetworkService.findDeviceFirewallNATs(token,
                        new AsyncCallback<List<GwtFirewallNatEntry>>() {

                    @Override
                    public void onFailure(Throwable caught) {
                        EntryClassUi.hideWaitModal();
                        FailureHandler.handle(caught);
                    }

                    @Override
                    public void onSuccess(List<GwtFirewallNatEntry> result) {
                        for (GwtFirewallNatEntry pair : result) {
                            NatTabUi.this.natDataProvider.getList().add(pair);
                        }
                        refreshTable();

                        NatTabUi.this.apply.setEnabled(false);
                        EntryClassUi.hideWaitModal();
                    }
                });
            }
        });
    }

    @Override
    public boolean isDirty() {
        return this.m_dirty;
    }

    @Override
    public void setDirty(boolean b) {
        this.m_dirty = b;
    }

    @Override
    public boolean isValid() {
        // TODO Auto-generated method stub
        return false;
    }

    //
    // Private methods
    //
    private void initTable() {

        TextColumn<GwtFirewallNatEntry> col1 = new TextColumn<GwtFirewallNatEntry>() {

            @Override
            public String getValue(GwtFirewallNatEntry object) {
                if (object.getInInterface() != null) {
                    return String.valueOf(object.getInInterface());
                } else {
                    return "";
                }
            }
        };
        col1.setCellStyleNames("status-table-row");
        this.natGrid.addColumn(col1, MSGS.firewallNatInInterface());

        TextColumn<GwtFirewallNatEntry> col2 = new TextColumn<GwtFirewallNatEntry>() {

            @Override
            public String getValue(GwtFirewallNatEntry object) {
                if (object.getOutInterface() != null) {
                    return String.valueOf(object.getOutInterface());
                } else {
                    return "";
                }
            }
        };
        col2.setCellStyleNames("status-table-row");
        this.natGrid.addColumn(col2, MSGS.firewallNatOutInterface());

        TextColumn<GwtFirewallNatEntry> col3 = new TextColumn<GwtFirewallNatEntry>() {

            @Override
            public String getValue(GwtFirewallNatEntry object) {
                if (object.getProtocol() != null) {
                    return String.valueOf(object.getProtocol());
                } else {
                    return "";
                }
            }
        };
        col3.setCellStyleNames("status-table-row");
        this.natGrid.addColumn(col3, MSGS.firewallNatProtocol());

        TextColumn<GwtFirewallNatEntry> col4 = new TextColumn<GwtFirewallNatEntry>() {

            @Override
            public String getValue(GwtFirewallNatEntry object) {
                if (object.getSourceNetwork() != null) {
                    return String.valueOf(object.getSourceNetwork());
                } else {
                    return "";
                }
            }
        };
        col4.setCellStyleNames("status-table-row");
        this.natGrid.addColumn(col4, MSGS.firewallNatSourceNetwork());

        TextColumn<GwtFirewallNatEntry> col5 = new TextColumn<GwtFirewallNatEntry>() {

            @Override
            public String getValue(GwtFirewallNatEntry object) {
                if (object.getDestinationNetwork() != null) {
                    return String.valueOf(object.getDestinationNetwork());
                } else {
                    return "";
                }
            }
        };
        col5.setCellStyleNames("status-table-row");
        this.natGrid.addColumn(col5, MSGS.firewallNatDestinationNetwork());

        TextColumn<GwtFirewallNatEntry> col6 = new TextColumn<GwtFirewallNatEntry>() {

            @Override
            public String getValue(GwtFirewallNatEntry object) {
                if (object.getMasquerade() != null) {
                    return String.valueOf(object.getMasquerade());
                } else {
                    return "";
                }
            }
        };
        col6.setCellStyleNames("status-table-row");
        this.natGrid.addColumn(col6, MSGS.firewallNatMasquerade());

        this.natDataProvider.addDataDisplay(this.natGrid);
        this.natGrid.setSelectionModel(this.selectionModel);
    }

    private void refreshTable() {
        int size = this.natDataProvider.getList().size();
        this.natGrid.setVisibleRange(0, size);
        this.natDataProvider.flush();

        if (this.natDataProvider.getList().isEmpty()) {
            this.natGrid.setVisible(false);
            this.notification.setVisible(true);
            this.notification.setText(MSGS.firewallPortForwardTableNoPorts());
        } else {
            this.natGrid.setVisible(true);
            this.notification.setVisible(false);
        }
        this.natGrid.redraw();
    }

    // Initialize tab buttons
    private void initButtons() {
        initApplyButton();

        initCreateButton();

        initEditButton();

        initDeleteButton();
    }

    private void initDeleteButton() {
        this.delete.setText(MSGS.deleteButton());
        this.delete.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                final GwtFirewallNatEntry selection = NatTabUi.this.selectionModel.getSelectedObject();
                if (selection != null) { // TODO: this part should be structured the same as the other firewall tabs
                    final Modal confirm = new Modal();
                    ModalBody confirmBody = new ModalBody();
                    ModalFooter confirmFooter = new ModalFooter();

                    confirm.setTitle(MSGS.confirm());
                    confirmBody.add(new Span(MSGS.firewallNatDeleteConfirmation(selection.getInInterface())));
                    confirmFooter.add(new Button(MSGS.yesButton(), new ClickHandler() {

                        @Override
                        public void onClick(ClickEvent event) {
                            NatTabUi.this.natDataProvider.getList().remove(selection);
                            refreshTable();
                            NatTabUi.this.apply.setEnabled(true);
                            confirm.hide();
                            setDirty(true);
                        }
                    }));

                    confirmFooter.add(new Button(MSGS.noButton(), new ClickHandler() {

                        @Override
                        public void onClick(ClickEvent event) {
                            confirm.hide();
                        }
                    }));

                    confirm.add(confirmBody);
                    confirm.add(confirmFooter);
                    confirm.show();
                }
            }
        });
    }

    private void initEditButton() {
        this.edit.setText(MSGS.editButton());
        this.edit.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                GwtFirewallNatEntry selection = NatTabUi.this.selectionModel.getSelectedObject();
                if (selection != null) {
                    showModal(selection);
                }
            }
        });
        this.natForm.addHideHandler(new ModalHideHandler() {

            @Override
            public void onHide(ModalHideEvent evt) {
                if (NatTabUi.this.editNatEntry != null) {
                    GwtFirewallNatEntry oldEntry = NatTabUi.this.selectionModel.getSelectedObject();
                    NatTabUi.this.natDataProvider.getList().remove(oldEntry);
                    if (!duplicateEntry(NatTabUi.this.editNatEntry)) {
                        NatTabUi.this.natDataProvider.getList().add(NatTabUi.this.editNatEntry);
                        NatTabUi.this.natDataProvider.flush();
                        NatTabUi.this.apply.setEnabled(true);
                        NatTabUi.this.editNatEntry = null;
                    } else {	// end duplicate
                        NatTabUi.this.natDataProvider.getList().add(oldEntry);
                        NatTabUi.this.natDataProvider.flush();
                    }
                }
            }
        });
    }

    private void initCreateButton() {
        this.create.setText(MSGS.newButton());
        this.create.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                showModal(null);
            }
        });
        this.natForm.addHideHandler(new ModalHideHandler() {

            @Override
            public void onHide(ModalHideEvent evt) {
                if (NatTabUi.this.newNatEntry != null && !duplicateEntry(NatTabUi.this.newNatEntry)) {
                    NatTabUi.this.natDataProvider.getList().add(NatTabUi.this.newNatEntry);
                    refreshTable();
                    NatTabUi.this.apply.setEnabled(true);
                    NatTabUi.this.newNatEntry = null;
                }
            }
        });
    }

    private void initApplyButton() {
        this.apply.setText(MSGS.firewallApply());
        this.apply.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                List<GwtFirewallNatEntry> intermediateList = NatTabUi.this.natDataProvider.getList();
                ArrayList<GwtFirewallNatEntry> tempList = new ArrayList<GwtFirewallNatEntry>();
                final List<GwtFirewallNatEntry> updatedNatConf = tempList;
                for (GwtFirewallNatEntry entry : intermediateList) {
                    tempList.add(entry);
                }

                if (updatedNatConf != null) {
                    EntryClassUi.showWaitModal();
                    NatTabUi.this.gwtXSRFService.generateSecurityToken(new AsyncCallback<GwtXSRFToken>() {

                        @Override
                        public void onFailure(Throwable ex) {
                            EntryClassUi.hideWaitModal();
                            FailureHandler.handle(ex);
                        }

                        @Override
                        public void onSuccess(GwtXSRFToken token) {
                            NatTabUi.this.gwtNetworkService.updateDeviceFirewallNATs(token, updatedNatConf,
                                    new AsyncCallback<Void>() {

                                @Override
                                public void onFailure(Throwable caught) {
                                    EntryClassUi.hideWaitModal();
                                    FailureHandler.handle(caught);
                                }

                                @Override
                                public void onSuccess(Void result) {
                                    setDirty(false);
                                    NatTabUi.this.apply.setEnabled(false);
                                    EntryClassUi.hideWaitModal();
                                }
                            });
                        }
                    });
                }
            }
        });
    }

    private void initModal() {
        // Handle Buttons
        this.cancel.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                NatTabUi.this.natForm.hide();
            }
        });

        this.submit.addClickHandler(new ClickHandler() {

            @Override
            public void onClick(ClickEvent event) {
                checkFieldsValues();

                if (NatTabUi.this.groupInput.getValidationState() == ValidationState.ERROR
                        || NatTabUi.this.groupOutput.getValidationState() == ValidationState.ERROR
                        || NatTabUi.this.groupSource.getValidationState() == ValidationState.ERROR
                        || NatTabUi.this.groupDestination.getValidationState() == ValidationState.ERROR) {
                    return;
                }
                // Fetch form data
                GwtFirewallNatEntry natEntry = new GwtFirewallNatEntry();
                natEntry.setInInterface(NatTabUi.this.input.getText());
                natEntry.setOutInterface(NatTabUi.this.output.getText());
                natEntry.setProtocol(NatTabUi.this.protocol.getSelectedItemText());
                natEntry.setSourceNetwork(NatTabUi.this.source.getText());
                natEntry.setDestinationNetwork(NatTabUi.this.destination.getText());
                natEntry.setMasquerade(NatTabUi.this.enable.getSelectedItemText());

                if (NatTabUi.this.submit.getId().equals("new")) {
                    NatTabUi.this.newNatEntry = natEntry;
                    NatTabUi.this.editNatEntry = null;
                } else if (NatTabUi.this.submit.getId().equals("edit")) {
                    NatTabUi.this.editNatEntry = natEntry;
                    NatTabUi.this.newNatEntry = null;
                }
                NatTabUi.this.natForm.hide();

                setDirty(true);
            }

        });
    }

    private void showModal(final GwtFirewallNatEntry existingEntry) {
        if (existingEntry == null) {
            this.natForm.setTitle(MSGS.firewallNatFormInformation());
        } else {
            this.natForm.setTitle(MSGS.firewallNatFormUpdate(existingEntry.getOutInterface()));
        }

        setModalFieldsLabels();

        setModalFieldsValues(existingEntry);

        setModalFieldsTooltips();

        setModalFieldsHandlers();

        if (existingEntry == null) {
            this.submit.setId("new");
        } else {
            this.submit.setId("edit");
        }

        this.natForm.show();
    }

    private void setModalFieldsHandlers() {
        // Set up validation
        this.input.addBlurHandler(new BlurHandler() {

            @Override
            public void onBlur(BlurEvent event) {
                if (!NatTabUi.this.input.getText().trim().matches(FieldType.ALPHANUMERIC.getRegex())
                        || NatTabUi.this.input.getText().trim().isEmpty()) {
                    NatTabUi.this.groupInput.setValidationState(ValidationState.ERROR);
                } else {
                    NatTabUi.this.groupInput.setValidationState(ValidationState.NONE);
                }
            }
        });
        this.output.addBlurHandler(new BlurHandler() {

            @Override
            public void onBlur(BlurEvent event) {
                if (!NatTabUi.this.output.getText().trim().matches(FieldType.ALPHANUMERIC.getRegex())
                        || NatTabUi.this.output.getText().trim().isEmpty()) {
                    NatTabUi.this.groupOutput.setValidationState(ValidationState.ERROR);
                } else {
                    NatTabUi.this.groupOutput.setValidationState(ValidationState.NONE);
                }
            }
        });
        this.source.addBlurHandler(new BlurHandler() {

            @Override
            public void onBlur(BlurEvent event) {
                if (!NatTabUi.this.source.getText().trim().isEmpty()
                        && !NatTabUi.this.source.getText().trim().matches(FieldType.NETWORK.getRegex())) {
                    NatTabUi.this.groupSource.setValidationState(ValidationState.ERROR);
                } else {
                    NatTabUi.this.groupSource.setValidationState(ValidationState.NONE);
                }
            }
        });
        this.destination.addBlurHandler(new BlurHandler() {

            @Override
            public void onBlur(BlurEvent event) {
                if (!NatTabUi.this.destination.getText().trim().isEmpty()
                        && !NatTabUi.this.destination.getText().trim().matches(FieldType.NETWORK.getRegex())) {
                    NatTabUi.this.groupDestination.setValidationState(ValidationState.ERROR);
                } else {
                    NatTabUi.this.groupDestination.setValidationState(ValidationState.NONE);
                }
            }
        });
    }

    private void setModalFieldsValues(final GwtFirewallNatEntry existingEntry) {
        // populate existing values
        if (existingEntry != null) {
            this.input.setText(existingEntry.getInInterface());
            this.output.setText(existingEntry.getOutInterface());
            this.source.setText(existingEntry.getSourceNetwork());
            this.destination.setText(existingEntry.getDestinationNetwork());
            for (int i = 0; i < this.protocol.getItemCount(); i++) {
                if (existingEntry.getProtocol().equals(this.protocol.getItemText(i))) {
                    this.protocol.setSelectedIndex(i);
                    break;
                }
            }

            for (int i = 0; i < this.enable.getItemCount(); i++) {
                if (existingEntry.getMasquerade().equals(this.enable.getItemText(i))) {
                    this.enable.setSelectedIndex(i);
                    break;
                }
            }
        } else {
            this.input.setText("");
            this.output.setText("");
            this.source.setText("");
            this.destination.setText("");

            this.protocol.setSelectedIndex(0);
            this.enable.setSelectedIndex(0);
        }
    }

    private void setModalFieldsTooltips() {
        // set Tooltips
        this.tooltipInput.setTitle(MSGS.firewallNatFormInputInterfaceToolTip());
        this.tooltipOutput.setTitle(MSGS.firewallNatFormOutputInterfaceToolTip());
        this.tooltipProtocol.setTitle(MSGS.firewallNatFormProtocolToolTip());
        this.tooltipSource.setTitle(MSGS.firewallNatFormSourceNetworkToolTip());
        this.tooltipDestination.setTitle(MSGS.firewallNatFormDestinationNetworkToolTip());
        this.tooltipEnable.setTitle(MSGS.firewallNatFormMasqueradingToolTip());
        this.tooltipInput.reconfigure();
        this.tooltipOutput.reconfigure();
        this.tooltipProtocol.reconfigure();
        this.tooltipSource.reconfigure();
        this.tooltipDestination.reconfigure();
        this.tooltipEnable.reconfigure();
    }

    private void setModalFieldsLabels() {
        // set Labels
        this.labelInput.setText(MSGS.firewallNatFormInInterfaceName() + "*");
        this.labelOutput.setText(MSGS.firewallNatFormOutInterfaceName() + "*");
        this.labelProtocol.setText(MSGS.firewallNatFormProtocol());
        this.labelSource.setText(MSGS.firewallNatFormSourceNetwork());
        this.labelDestination.setText(MSGS.firewallNatFormDestinationNetwork());
        this.labelEnable.setText(MSGS.firewallNatFormMasquerade());
        this.submit.setText(MSGS.submitButton());
        this.cancel.setText(MSGS.cancelButton());

        // set ListBox
        this.protocol.clear();
        for (GwtFirewallNatProtocol prot : GwtFirewallNatProtocol.values()) {
            this.protocol.addItem(prot.name());
        }
        this.enable.clear();
        for (GwtFirewallNatMasquerade masquerade : GwtFirewallNatMasquerade.values()) {
            this.enable.addItem(masquerade.name());
        }
    }

    private boolean duplicateEntry(GwtFirewallNatEntry firewallNatEntry) {
        boolean isDuplicateEntry = false;
        List<GwtFirewallNatEntry> entries = this.natDataProvider.getList();
        if (entries != null && firewallNatEntry != null) {
            for (GwtFirewallNatEntry entry : entries) {
                String sourceNetwork = entry.getSourceNetwork() != null ? entry.getSourceNetwork() : "0.0.0.0/0";
                String destinationNetwork = entry.getDestinationNetwork() != null ? entry.getDestinationNetwork()
                        : "0.0.0.0/0";
                String newSourceNetwork = firewallNatEntry.getSourceNetwork() != null
                        ? firewallNatEntry.getSourceNetwork() : "0.0.0.0/0";
                String newDestinationNetwork = firewallNatEntry.getDestinationNetwork() != null
                        ? firewallNatEntry.getDestinationNetwork() : "0.0.0.0/0";

                if (entry.getInInterface().equals(firewallNatEntry.getInInterface())
                        && entry.getOutInterface().equals(firewallNatEntry.getOutInterface())
                        && entry.getProtocol().equals(firewallNatEntry.getProtocol())
                        && sourceNetwork.equals(newSourceNetwork) && destinationNetwork.equals(newDestinationNetwork)) {
                    isDuplicateEntry = true;
                    break;
                }
            }
        }

        return isDuplicateEntry;
    }

    private void checkFieldsValues() {
        // make the required fields in error state by default
        if (this.input.getText() == null || "".equals(this.input.getText().trim())) {
            this.groupInput.setValidationState(ValidationState.ERROR);
        }
        if (this.output.getText() == null || "".equals(this.output.getText().trim())) {
            this.groupOutput.setValidationState(ValidationState.ERROR);
        }
    }
}
